Skip to content

feat: add HTTP subscription support via polling#32

Merged
0xNeshi merged 39 commits intoOpenZeppelin:mainfrom
smartprogrammer93:feat/http-subscription
Mar 5, 2026
Merged

feat: add HTTP subscription support via polling#32
0xNeshi merged 39 commits intoOpenZeppelin:mainfrom
smartprogrammer93:feat/http-subscription

Conversation

@smartprogrammer93
Copy link
Contributor

@smartprogrammer93 smartprogrammer93 commented Jan 29, 2026

Summary

Implements HTTP subscription support via polling, allowing HTTP providers to participate in block subscriptions alongside WebSocket providers.

Resolves #23

Features

  • HTTP polling subscriptions: Uses Alloy's watch_blocks() API with eth_newBlockFilter + eth_getFilterChanges for efficient block polling
  • Seamless integration: Works transparently with existing RobustSubscription API - callers use the same subscribe_blocks() and recv() interface
  • Automatic deduplication: Alloy's filter-based approach prevents duplicate block emissions
  • Failover support: HTTP providers can participate in subscription failover chains (WS → HTTP and HTTP → WS)
  • Configuration propagation: Poll interval, timeouts, and buffer capacity settings carry over during failover

Usage

Enable the http-subscription feature flag:

[dependencies]
robust-provider = { version = "...", features = ["http-subscription"] }

Build with HTTP subscription support:

let provider = RobustProviderBuilder::new(ws_provider)
    .fallback(http_provider)
    .allow_http_subscriptions(true)
    .poll_interval(Duration::from_secs(12))
    .subscription_timeout(Duration::from_secs(60))
    .build()
    .await?;

// Use exactly like before - HTTP polling is transparent
let mut subscription = provider.subscribe_blocks().await?;
while let Ok(block) = subscription.recv().await {
    println!("Block {}", block.number);
}

Breaking Changes

None. Feature is behind http-subscription flag and disabled by default.


Thanks to @PoulavBhowmick03 for the massive improvements to this implementation.

Closes #23

Implements OpenZeppelin#23 - Support HTTP Subscription

This PR adds the ability for HTTP providers to participate in block
subscriptions via polling, enabling use cases where WebSocket connections
are not available (e.g., behind load balancers).

## Changes

### New Feature (behind `http-subscription` feature flag)
- Add `HttpPollingSubscription` that polls `eth_getBlockByNumber(latest)`
  at configurable intervals
- Add `SubscriptionBackend` enum to handle both WebSocket and HTTP backends
- Add `poll_interval()` and `allow_http_subscriptions()` builder methods
- Seamless failover between mixed WS/HTTP provider chains

### Files
- `src/robust_provider/http_subscription.rs` - New HTTP polling module
- `src/robust_provider/subscription.rs` - Unified backend handling
- `src/robust_provider/builder.rs` - New configuration options
- `src/robust_provider/provider.rs` - Updated subscribe_blocks()
- `Cargo.toml` - Added `http-subscription` feature flag

## Usage

```rust
let robust = RobustProviderBuilder::new(http_provider)
    .allow_http_subscriptions(true)
    .poll_interval(Duration::from_secs(12))
    .build()
    .await?;

let mut sub = robust.subscribe_blocks().await?;
```

## Trade-offs (documented)
- Latency: up to `poll_interval` delay for block detection
- RPC Load: one call per `poll_interval`
- Feature-gated to ensure explicit opt-in

Closes OpenZeppelin#23
@smartprogrammer93 smartprogrammer93 marked this pull request as draft January 29, 2026 14:14
Add comprehensive integration tests in tests/http_subscription.rs:

- test_http_subscription_basic_flow
- test_http_subscription_multiple_blocks
- test_http_subscription_as_stream
- test_failover_from_ws_to_http
- test_failover_from_http_to_ws
- test_mixed_provider_chain_failover
- test_http_reconnects_to_ws_primary
- test_http_only_no_ws_providers
- test_http_subscription_disabled_falls_back_to_ws
- test_custom_poll_interval

All tests gated behind #[cfg(feature = "http-subscription")]
Audit findings addressed:

Unit tests (http_subscription.rs):
- Improved test_http_polling_deduplication with better verification
- Renamed test_http_polling_handles_drop → test_http_polling_stops_on_drop
  with clearer verification logic
- Added test_http_subscription_error_types for all error variants
- Added test_http_polling_close_method for close() functionality

Integration tests (tests/http_subscription.rs) - rewritten:
- Removed broken test_http_reconnects_to_ws_primary (was meaningless)
- Removed flawed test_custom_poll_interval, replaced with
  test_poll_interval_is_respected (measures correctly)
- Renamed tests for clarity on what they actually verify
- Added test_http_disabled_no_ws_fails (negative test case)
- Added test_all_providers_fail_returns_error (error handling)
- Added test_http_subscription_survives_temporary_errors
- Added test_http_polling_deduplication (integration level)
- Fixed failover tests to verify behavior correctly
- Removed fragile 'pre-mine to distinguish providers' hacks

Test count: 73 total (19 unit + 12 http integration + 24 subscription + 18 eth)
Tests verify that RPC calls (not just subscriptions) properly:
- Failover to fallback providers when primary dies
- Cycle through multiple fallbacks
- Return errors when all providers exhausted
- Don't retry non-retryable errors (BlockNotFound)
- Complete within bounded time when providers unavailable
- Work correctly for various RPC methods (get_accounts, get_balance, get_block)
Fixes two bugs in HTTP subscription handling:

1. http_config now uses configured values from RobustProviderBuilder
   instead of defaults when a WebSocket subscription is created first.
   This ensures poll_interval, call_timeout, and buffer_capacity are
   respected when failing over to HTTP.

2. HTTP reconnection now validates the provider is reachable before
   claiming success. Uses a short 50ms timeout to quickly fail and
   not block the failover process.

Also fixes test timing in test_failover_http_to_ws_on_provider_death
to mine before subscription timeout instead of after.

Adds two new tests:
- test_poll_interval_propagated_from_builder: verifies config propagation
- test_http_reconnect_validates_provider: verifies reconnect validation
@smartprogrammer93 smartprogrammer93 force-pushed the feat/http-subscription branch 2 times, most recently from f94c84d to b6001af Compare January 29, 2026 15:55
PoulavBhowmick03 and others added 10 commits February 3, 2026 00:10
Implements OpenZeppelin#23 - Support HTTP Subscription

This PR adds the ability for HTTP providers to participate in block
subscriptions via polling, enabling use cases where WebSocket connections
are not available (e.g., behind load balancers).

- Add `HttpPollingSubscription` that polls `eth_getBlockByNumber(latest)`
  at configurable intervals
- Add `SubscriptionBackend` enum to handle both WebSocket and HTTP backends
- Add `poll_interval()` and `allow_http_subscriptions()` builder methods
- Seamless failover between mixed WS/HTTP provider chains

- `src/robust_provider/http_subscription.rs` - New HTTP polling module
- `src/robust_provider/subscription.rs` - Unified backend handling
- `src/robust_provider/builder.rs` - New configuration options
- `src/robust_provider/provider.rs` - Updated subscribe_blocks()
- `Cargo.toml` - Added `http-subscription` feature flag

```rust
let robust = RobustProviderBuilder::new(http_provider)
    .allow_http_subscriptions(true)
    .poll_interval(Duration::from_secs(12))
    .build()
    .await?;

let mut sub = robust.subscribe_blocks().await?;
```

- Latency: up to `poll_interval` delay for block detection
- RPC Load: one call per `poll_interval`
- Feature-gated to ensure explicit opt-in

Closes OpenZeppelin#23
Add comprehensive integration tests in tests/http_subscription.rs:

- test_http_subscription_basic_flow
- test_http_subscription_multiple_blocks
- test_http_subscription_as_stream
- test_failover_from_ws_to_http
- test_failover_from_http_to_ws
- test_mixed_provider_chain_failover
- test_http_reconnects_to_ws_primary
- test_http_only_no_ws_providers
- test_http_subscription_disabled_falls_back_to_ws
- test_custom_poll_interval

All tests gated behind #[cfg(feature = "http-subscription")]
Audit findings addressed:

Unit tests (http_subscription.rs):
- Improved test_http_polling_deduplication with better verification
- Renamed test_http_polling_handles_drop → test_http_polling_stops_on_drop
  with clearer verification logic
- Added test_http_subscription_error_types for all error variants
- Added test_http_polling_close_method for close() functionality

Integration tests (tests/http_subscription.rs) - rewritten:
- Removed broken test_http_reconnects_to_ws_primary (was meaningless)
- Removed flawed test_custom_poll_interval, replaced with
  test_poll_interval_is_respected (measures correctly)
- Renamed tests for clarity on what they actually verify
- Added test_http_disabled_no_ws_fails (negative test case)
- Added test_all_providers_fail_returns_error (error handling)
- Added test_http_subscription_survives_temporary_errors
- Added test_http_polling_deduplication (integration level)
- Fixed failover tests to verify behavior correctly
- Removed fragile 'pre-mine to distinguish providers' hacks

Test count: 73 total (19 unit + 12 http integration + 24 subscription + 18 eth)
Tests verify that RPC calls (not just subscriptions) properly:
- Failover to fallback providers when primary dies
- Cycle through multiple fallbacks
- Return errors when all providers exhausted
- Don't retry non-retryable errors (BlockNotFound)
- Complete within bounded time when providers unavailable
- Work correctly for various RPC methods (get_accounts, get_balance, get_block)
Fixes two bugs in HTTP subscription handling:

1. http_config now uses configured values from RobustProviderBuilder
   instead of defaults when a WebSocket subscription is created first.
   This ensures poll_interval, call_timeout, and buffer_capacity are
   respected when failing over to HTTP.

2. HTTP reconnection now validates the provider is reachable before
   claiming success. Uses a short 50ms timeout to quickly fail and
   not block the failover process.

Also fixes test timing in test_failover_http_to_ws_on_provider_death
to mine before subscription timeout instead of after.

Adds two new tests:
- test_poll_interval_propagated_from_builder: verifies config propagation
- test_http_reconnect_validates_provider: verifies reconnect validation
@smartprogrammer93 smartprogrammer93 marked this pull request as ready for review February 5, 2026 17:17
Copy link
Collaborator

@0xNeshi 0xNeshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

First "shallower" review iteration, will dive deeper in the following ones

@0xNeshi
Copy link
Collaborator

0xNeshi commented Feb 11, 2026

Also, resolve conflicts please

- Add http-subscription feature to VSCode settings for rust-analyzer
- Make HTTP_RECONNECT_VALIDATION_TIMEOUT public
- Fix HTTP subscription fallback: try fallback providers when primary HTTP fails
- Fix buffer_capacity: use mpsc channel with configured capacity
- Fix error documentation: use proper error list with stars
- Remove unused imports (FutureExt, Stream)
- Add pub const DEFAULT_CALL_TIMEOUT (30 seconds)
- Add pub const DEFAULT_BUFFER_CAPACITY (128)
- Update rustdocs to reference the new constants
- Update Default impl to use constants instead of magic numbers
- Update test to use constants for consistency

Addresses reviewer comment on line 105 about converting constants
into actual pub const values.
Copy link
Collaborator

@0xNeshi 0xNeshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After all there are still some things to address

@PoulavBhowmick03
Copy link
Contributor

@0xNeshi are there any further tests to be written post your PR?

Copy link
Collaborator

@0xNeshi 0xNeshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're almost ready to merge. Running all the workflows to ensure all's fine.

Once merged, we'll create a new release @LeoPatOZ @pepebndc

@0xNeshi
Copy link
Collaborator

0xNeshi commented Mar 4, 2026

The failing test is flaky, will be handled as part of #59

@0xNeshi
Copy link
Collaborator

0xNeshi commented Mar 4, 2026

@smartprogrammer93 we'd like to get this merged for you asap, I will create a new PR addressing all of the comments. Merge that, and we merge this.

@PoulavBhowmick03
Copy link
Contributor

@smartprogrammer93 we'd like to get this merged for you asap, I will create a new PR addressing all of the comments. Merge that, and we merge this.

Should I address the comments you made or are you making a new PR to this?

@0xNeshi
Copy link
Collaborator

0xNeshi commented Mar 4, 2026

@smartprogrammer93 we'd like to get this merged for you asap, I will create a new PR addressing all of the comments. Merge that, and we merge this.

Should I address the comments you made or are you making a new PR to this?

I'm making it, will have it up in the next 10-15 mins

@0xNeshi
Copy link
Collaborator

0xNeshi commented Mar 4, 2026

Copy link
Collaborator

@0xNeshi 0xNeshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we were able to finalize this, thanks for the good work (and patience) @smartprogrammer93

@0xNeshi 0xNeshi merged commit 29276a7 into OpenZeppelin:main Mar 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support HTTP Subscription

4 participants